11 IO
这篇笔记介绍lecture18的内容,即系统级I/O。I/O指软件和外界设备之间的数据交流。标准I/O库指用户态或更高级的I/O函数,而Unix I/O指内核态提供的I/O函数。
文件
Unix中的文件是一系列字节。所有I/O设备都被视作文件,使得Unix能提供一个简单的应用程序接口。所有输入输出都通过读写相应文件来运行。
目录结构
Linux内核将所有文件组织在一个单一的目录层次结构中,以根目录(/)为锚点。每个进程都有当前的工作目录。
文件类型
常规文件可以包含任意数据。它分为文本文件(只包含ASCII或Unicode字符)和二进制文件(其他)两类,这些文件从内核角度上看没有区别。另外,文本文件的每行以换行符结束(LF),值为0x0a。
目录是包含一个链接(文件入口)数组的文件,每个链接将一个文件名映射到一个文件。每个目录至少包含两个文件入口,分别是 . 和 .. 。
套接字(socket)是一个用于通过网络与其它进程通信的文件。
此外,还有命名管道(pipes)、符号链接、字符和块设备等文件。
文件操作
打开文件
当某个应用程序需要获取I/O设备时,内核会打开相关文件、返回描述符(一个小的非负整数,在文件的所有后续操作中标识该文件)并跟踪这个文件的所有信息(为所有打开的文件维护一个文件位置k,初值为0)。
为了实现这些操作,内核需要知道返回哪个整数,还需要知道在存储文件位置(cursor)、文件是否存在 、文件大小等信息。这些信息分别通过进程ID池、打开文件的数据结构、文件系统、元数据等获得。

每个进程都有独立的描述符表,描述表的条目由进程的打开文件描述符索引。每个描述符指向文件表中的一个条目。
文件表表示打开文件的集合,被所有进程共享。每个文件表条目包含当前文件位置、指向它的描述符条目的引用数量、一个指向v节点表中条目的指针。当引用数量变为0时,在文件表中删除这个条目。
v节点表也由所有进程共享,每个条目包含一个文件的主要信息。它位于主内存中(元数据被缓存)。
在应用程序获取I/O设备时,它只保存描述符。通过 seek 操作,应用程序可以显示设置当前文件位置。
// return new file descriptor if OK, -1 on error
int open(char *filename, int flags, mode_t mode);

可以同时使用多个flags和mode,用管道符连接。
通过 umask 函数可以为进程设置掩码,在创建文件时,掩码中的权限会被屏蔽。
#define DEF_MODE S_IRUSR | S_IWUSR | \
S_IRGRP | S_IWGRP | \
S_IROTH | S_IWOTH
#define DEF_UMASK S_IWGRP | S_IWOTH
umask(DEF_UMASK);
fd = open (“foot.txt”, O_CREAT|O_TRUNC|O_WRONLY, DEF_MODE);
例如,这里 foot.txt 文件的权限是 DEF_MODE & (~DEF_UMASK) 。
关闭文件
关闭文件时,内核释放打开文件时创建的结构, 并将描述符恢复到可用描述池。下一个打开的文件会接收到池中最小的可用描述符。
终止时,内核的默认动作是关闭所有打开的文件并释放它们的内存资源。
// return 0 if OK, -1 on error
int close(int fd);
读写操作
读写操作的本质是将复制字符,读是复制字符到 buf ,写则反之。
// number of bytes if OK, 0 on EOF, -1 on error
ssize_t read(int fd, void *buf, size_t count);
// number of bytes if OK, -1 on error
ssize_t write(int fd, const void *buf, size_t count);
注意这里的返回值类型是 ssize_t 而不是 size_t ,因为返回值可能是负数。
读取到file position k 后 m 个字节的位置。如果 size (存储在原数据中)小于需要读取的字符数,触发EOF,并返回short count。此外,从终端或网络读取数据也会返回short count。
Lseek
// new cursor position if OK, -1 on error
off_t lseek(int fd, off_t offset, int whence);
这个函数可以调整文件位置值。 fd 代表需要修改的文件, offset 指偏移量(可以是负数), whence 代表起始位置(SEEK_SET、SEEK_CUR、SEEK_END分别对应文件开始、当前位置、结尾)。注意文件位置是第一个被读取的位置,从0开始。
int main()
{
int fd;
char c;
fd = open(“foobar.txt”, O_RDONLY, 0);
lseek(fd, 3, SEEK_SET);
read(fd, &c, 1);
printf("char = %c\n", c);
exit(0);
}
foobar.txt 中存储的内容是“foobar”,上面代码的输出是‘b’。